﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace GamingNetCode
{
    public abstract class Server : IDisposable
    {
        protected const String SERVERNAME = "~SERVER~";

        protected int mPort;
        protected Thread ListenerThread;
        protected ManualResetEvent ListerEvent;
        protected TcpListener TL;
        protected Dictionary<String, TcpClient> ClientList;
        protected Dictionary<String, Thread> ClientThreadList;
        protected Dictionary<String, ManualResetEvent> ClientEventList;
        protected Dictionary<string, DateTime> LastMessageReceived;
        protected Thread ClientStatusThread;
        protected ManualResetEvent ClientStatusEvent;
        public event MessagePasser ControllerMessageSent;

        public Server(int pPort)
        {
            this.mPort = pPort;
            this.ClientEventList = new Dictionary<String, ManualResetEvent>();
            this.ClientList = new Dictionary<String, TcpClient>();
            this.ClientThreadList = new Dictionary<String, Thread>();
            this.LastMessageReceived = new Dictionary<string, DateTime>();
        }

        public abstract void SendControllerMessage(string MessageType, params object[] data);

        ~Server()
        {
            try
            {
                this.Stop();
            }
            catch { }
        }

        protected void ShutDownClientChecker()
        {
            this.ClientStatusEvent.Set();
            this.ClientStatusThread.Join();
            this.ClientStatusThread = null;
            this.ClientStatusEvent = null;
        }

        protected void CheckClients()
        {
            while (!this.ClientStatusEvent.WaitOne(5000, false))
            {
                lock (this.LastMessageReceived)
                {
                    List<string> temp = new List<string>();
                    foreach (string s in this.LastMessageReceived.Keys)
                        temp.Add(s);

                    foreach (string s in temp)
                    {
                        if ((DateTime.Now - this.LastMessageReceived[s]).TotalSeconds >= 5 && (DateTime.Now - this.LastMessageReceived[s]).TotalSeconds < 10)
                        {
                            ClientCheckMessage CCM = new ClientCheckMessage(SERVERNAME);
                            this.WriteMessage(s, CCM);
                            Debug.WriteLine("Client " + s + " Has been unresponsive for 5 seconds or more, sending a client Check");
                        }
                        else if ((DateTime.Now - this.LastMessageReceived[s]).TotalSeconds >= 10)
                        {
                            this.ShutDownClient(s, true);
                            Debug.WriteLine("Client " + s + " Has been unresponsive for 10 seconds or mode, Disconnecting Client");
                        }
                    }
                }
            }
        }

        public virtual void Start()
        {
            this.ListenerThread = new Thread(StartListener);

            this.ListenerThread.Name = "New Connection Listener Thread";
            this.ListerEvent = new ManualResetEvent(false);

            this.ListenerThread.Start();

            this.ClientStatusThread = new Thread(CheckClients);
            this.ClientStatusThread.Name = "Client Status Thread";
            this.ClientStatusEvent = new ManualResetEvent(false);

            this.ClientStatusThread.Start();
        }

        protected void ShutDownClient(string Name, bool AnnounceToOthers)
        {
            lock (this.ClientEventList)
                this.ClientEventList[Name].Set();

            lock (this.ClientList)
                this.ClientList[Name].Close();

            lock (this.ClientThreadList)
                this.ClientThreadList[Name].Join();

            lock (this.ClientThreadList)
                this.ClientThreadList.Remove(Name);

            lock (this.ClientEventList)
                this.ClientEventList.Remove(Name);

            lock (this.ClientList)
                this.ClientList.Remove(Name);

            lock (this.LastMessageReceived)
                this.LastMessageReceived.Remove(Name);

            if (AnnounceToOthers)
                this.WriteMessage(new ClientDisconnectMessage(SERVERNAME, Name));
        }

        public virtual void Stop()
        {
            StopListener();

            this.ShutDownClientChecker();

            foreach (string s in this.ClientList.Keys)
            {
                try
                {
                    this.ShutDownClient(s, false);
                }
                catch { }
            }
        }

        protected void StopListener()
        {
            this.ListerEvent.Set();
            this.TL.Stop();
            this.ListenerThread.Join();

            this.ListerEvent = null;
            this.TL = null;
            this.ListenerThread = null;
        }

        protected virtual void StartListener()
        {
            TL = new TcpListener(IPAddress.Any, this.mPort);

            try
            {
                TL.Start();
                while (true)
                {
                    TcpClient C = TL.AcceptTcpClient();

                    String S = this.HandShake(C);

                    if (!string.IsNullOrEmpty(S))
                    {
                        Thread T = new Thread(delegate() { this.ClientListener(C, S); });
                        T.Name = "ClientListener Thread";

                        ManualResetEvent MER = new ManualResetEvent(false);

                        //Notify other connected clients of the new client
                        this.WriteMessage(new NewClientMessage(SERVERNAME, S));

                        lock (this.ClientThreadList)
                        {
                            this.ClientThreadList.Add(S, T);
                        }
                        lock (this.ClientList)
                        {
                            this.ClientList.Add(S, C);
                        }
                        lock (this.ClientEventList)
                        {
                            this.ClientEventList.Add(S, MER);
                        }

                        lock (this.LastMessageReceived)
                            this.LastMessageReceived.Add(S, DateTime.Now);

                        T.Start();

                        if (this.ListerEvent.WaitOne(0, false))
                            break;
                    }
                }
            }
            catch { }
        }

        protected virtual void ClientListener(TcpClient C, String Uname)
        {
            NetworkStream NS = C.GetStream();

            while (true)
            {
                try
                {
                    StringBuilder SB = new StringBuilder();

                    for (int i = 0; i < BaseTCPMessage.DATASTARTINDEX; i++)
                    {
                        int c = NS.ReadByte();

                        if (c == -1)
                            break;

                        SB.Append((char)c);
                    }

                    int MessageSize = BaseTCPMessage.GetMessageSize(SB.ToString());

                    for (int i = 0; i < MessageSize - BaseTCPMessage.DATASTARTINDEX; i++)
                    {
                        int c = NS.ReadByte();

                        if (c == -1)
                            break;

                        SB.Append((char)c);
                    }

                    this.HandleMessage(BaseTCPMessage.GenerateMessage(SB.ToString()));
                }
                catch (Exception e)
                {
                    break;
                }

                lock (this.ClientEventList)
                {
                    if (this.ClientEventList[Uname].WaitOne(0, false))
                        break;
                }

            }
        }

        protected void WriteMessage(String Target, BaseTCPMessage Message)
        {
            lock (this.ClientList)
            {
                try
                {
                    TcpClient C = this.ClientList[Target];
                    NetworkStream NS = C.GetStream();
                    NS.Write(Message.EncodeMessage(), 0, Message.EncodeMessage().Length);
                }
                catch
                {
                    Console.WriteLine("Error Finding Client for Target: " + Target);
                    return;
                }
            }
        }

        protected void WriteMessage(BaseTCPMessage Message)
        {
            foreach (String key in this.ClientList.Keys)
            {
                this.WriteMessage(key, Message);
            }
        }

        protected void WriteMessage(BaseTCPMessage Message, params String[] Targets)
        {
            foreach (String S in Targets)
                this.WriteMessage(S, Message);
        }

        protected void WriteMessage(BaseTCPMessage Message, List<String> Targets)
        {
            foreach (String S in Targets)
                this.WriteMessage(S, Message);
        }

        protected String HandShake(TcpClient C)
        {
            HandshakeMessage M = new HandshakeMessage("~SERVER~");

            NetworkStream NS = C.GetStream();

            NS.Write(M.EncodeMessage(), 0, M.EncodeMessage().Length);

            StringBuilder SB = new StringBuilder();

            for (int i = 0; i < BaseTCPMessage.DATASTARTINDEX; i++)
            {
                SB.Append((char)NS.ReadByte());
            }

            int MessageSize = BaseTCPMessage.GetMessageSize(SB.ToString());

            for (int i = 0; i < MessageSize - BaseTCPMessage.DATASTARTINDEX; i++)
                SB.Append((char)NS.ReadByte());

            M = new HandshakeMessage();

            M.DecodeMessage(SB.ToString());

            if (String.IsNullOrEmpty(M.Sender) || M.Sender == "~SERVER~" || this.ClientList.ContainsKey(M.Sender))
                return null;

            return M.Sender;
        }

        protected void HandleMessage(String Message)
        {
            this.HandleMessage(BaseTCPMessage.GenerateMessage(Message));
        }

        protected abstract void HandleMessage(BaseTCPMessage Message);

        public virtual void Dispose()
        {
            //this.Stop();
        }
    }
}
